﻿/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is tyzoh.jp code.
 *
 * The Initial Developer of the Original Code is Kazutaka Kurihara.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** */
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using Microsoft.Ink;
using System.Windows.Forms;
using System.Collections;
using System.Collections.Generic;

namespace Qurihara.QBasic
{
	public class InkUtil
	{
		public static void GetBoundingBoxFromInk(Control cont,ref Rectangle theR, InkCollector theIC)
		{
			// theR is  the bounding rectangle in ink space dimensions
			// Load the top left and bottom right points
			Point ptTL, ptBR;
			ptTL = ptBR = theR.Location;
			ptBR += theR.Size;
			// Find the edges of the bounding box in pixel space
			//theIC.Renderer.InkSpaceToPixel(formHandle,ref ptTL);
			//theIC.Renderer.InkSpaceToPixel(formHandle,ref ptBR);
			Graphics g = cont.CreateGraphics();
			theIC.Renderer.InkSpaceToPixel(g,ref ptTL);
			theIC.Renderer.InkSpaceToPixel(g,ref ptBR);
			g.Dispose();
			// Copy the out parameters
			theR.X = ptTL.X;
			theR.Y = ptTL.Y;
			theR.Width = ptBR.X - ptTL.X;
			theR.Height = ptBR.Y - ptTL.Y;
		}
//		public static void GetBoundingBoxFromInk(System.IntPtr formHandle,ref Rectangle theR, InkCollector theIC)
//		{
//			// theR is  the bounding rectangle in ink space dimensions
//			// Load the top left and bottom right points
//			Point ptTL, ptBR;
//			ptTL = ptBR = theR.Location;
//			ptBR += theR.Size;
//			// Find the edges of the bounding box in pixel space
//			theIC.Renderer.InkSpaceToPixel(formHandle,ref ptTL);
//			theIC.Renderer.InkSpaceToPixel(formHandle,ref ptBR);
//			// Copy the out parameters
//			theR.X = ptTL.X;
//			theR.Y = ptTL.Y;
//			theR.Width = ptBR.X - ptTL.X;
//			theR.Height = ptBR.Y - ptTL.Y;
//		}
		public static void GetViewBoundingBoxFromInk(Control cont,ref Rectangle theR, InkCollector theIC, View view)
		{
			// theR is  the bounding rectangle in ink space dimensions
			// Load the top left and bottom right points
			Point ptTL, ptBR;
			ptTL = ptBR = theR.Location;
			ptBR += theR.Size;
			// Find the edges of the bounding box in pixel space
			//theIC.Renderer.InkSpaceToPixel(formHandle,ref ptTL);
			//theIC.Renderer.InkSpaceToPixel(formHandle,ref ptBR);
			Graphics g = cont.CreateGraphics();
			theIC.Renderer.InkSpaceToPixel(g,ref ptTL);
			theIC.Renderer.InkSpaceToPixel(g,ref ptBR);
			// transform into View coordinate
			view.ScreenToView(ref ptTL);
			view.ScreenToView(ref ptBR);
			// Copy the out parameters
			theR.X = ptTL.X;
			theR.Y = ptTL.Y;
			theR.Width = ptBR.X - ptTL.X;
			theR.Height = ptBR.Y - ptTL.Y;
		}
//		public static void GetViewBoundingBoxFromInk(System.IntPtr formHandle,ref Rectangle theR, InkCollector theIC, View view)
//		{
//			// theR is  the bounding rectangle in ink space dimensions
//			// Load the top left and bottom right points
//			Point ptTL, ptBR;
//			ptTL = ptBR = theR.Location;
//			ptBR += theR.Size;
//			// Find the edges of the bounding box in pixel space
//			theIC.Renderer.InkSpaceToPixel(formHandle,ref ptTL);
//			theIC.Renderer.InkSpaceToPixel(formHandle,ref ptBR);
//			// transform into View coordinate
//			view.ScreenToView(ref ptTL);
//			view.ScreenToView(ref ptBR);
//			// Copy the out parameters
//			theR.X = ptTL.X;
//			theR.Y = ptTL.Y;
//			theR.Width = ptBR.X - ptTL.X;
//			theR.Height = ptBR.Y - ptTL.Y;
//		}

		public static void Stroke2Bitmap(ref Bitmap image,float xScale,float yScale,InkCollector ic,Stroke st)
		{
			Matrix m = new Matrix();
			Matrix wm = new Matrix();
			Graphics tg = Graphics.FromImage(image);
			tg.TranslateTransform(100,100,MatrixOrder.Append);
			// 現在のMatrixを保存
			ic.Renderer.GetViewTransform(ref wm);
			// 倍率は同じで描画領域の左上端に詰める。
			//m.Scale(1.0f,1.0f);  
			m.Scale(xScale,yScale);  
			m.Translate(-st.GetBoundingBox().X,-st.GetBoundingBox().Y,MatrixOrder.Append);
			// 等倍
			ic.Renderer.SetViewTransform(m);
			// インク情報を書き込む
			//ic.Renderer.Draw(tg,st,st.DrawingAttributes); ←ふちがおかしくなる。
			ic.Renderer.Draw(tg,st);
			// 表示用にMatrixを復元
			ic.Renderer.SetViewTransform(wm);
			m.Dispose();
			wm.Dispose();
		}
		/*
		public static void Strokes2Bitmap(ref Bitmap image,float xScale,float yScale,InkCollector ic,Strokes sts)
		{
			Matrix m = new Matrix();
			Matrix wm = new Matrix();
			Graphics tg = Graphics.FromImage(image);
			tg.TranslateTransform(100,100,MatrixOrder.Append);
			// 現在のMatrixを保存
			ic.Renderer.GetViewTransform(ref wm);
			//m.Scale(1.0f,1.0f);  
			m.Scale(xScale,yScale);
			//m.Translate(-sts.GetBoundingBox().X,-sts.GetBoundingBox().Y,MatrixOrder.Append);
			// 等倍
			ic.Renderer.SetViewTransform(m);
			// インク情報を書き込む
			//ic.Renderer.Draw(tg,st,st.DrawingAttributes); ←ふちがおかしくなる。
			ic.Renderer.Draw(tg,sts);
			// 表示用にMatrixを復元
			ic.Renderer.SetViewTransform(wm);
			m.Dispose();
			wm.Dispose();
		}
		*/

		public static Stroke GetStrokeFromID(Ink ink,int ID) //Very costly! Be careful to use!
		{
			Strokes ss = ink.Strokes;
			Stroke s;
			IEnumerator enume = ss.GetEnumerator();
			while(enume.MoveNext())
			{
				s = (Stroke)enume.Current;
				if (s.Id.Equals(ID))
				{
					return s;
				}
			}
			return null;
		}

		public static double Distance(Point a,Point b)
		{
			double x = a.X-b.X;
			double y = a.Y-b.Y;
			return Math.Sqrt(x*x+y*y);
		}
		public static double Length(Stroke s)
		{
			if (s.PacketCount < 2) return 0;
			double result = 0;
			Point [] p = s.GetPoints();
			for (int i=0;i<s.PacketCount-1;i++)
			{
				result += Distance(p[i],p[i+1]);
			}
			return result;
		}
		public static double DistanceBetweenStartAndEnd(Stroke s)
		{
			if (s.PacketCount < 2) return 0;
			return Distance(s.GetPoint(0),s.GetPoint(s.PacketCount-1));
		}

		//閉曲線かどうか
		public static bool IsClosed(Stroke s)
		{
			if (Length(s) / DistanceBetweenStartAndEnd(s) >10) return true;
			return false;
		}

		public static double Norm(Point a)
		{
			return Math.Sqrt(a.X * a.X + a.Y * a.Y);
		}
		public static double InnerProduct(Point a, Point b)
		{
			return (a.X * b.X + a.Y * b.Y);
		}
		public static double CrossProductScalar(Point a, Point b)
		{
			return (a.X*b.Y - a.Y*b.X);
		}
		public static double Criteria = 0.000001;
		public static bool IsZero(Point p)
		{
			if (Math.Abs(p.X) < Criteria && Math.Abs(p.Y) < Criteria) 
			{
				return true;
			}
			return false;
		}
		public static double Angle(Point a,Point b)
		{
			if (!IsZero(a) && !IsZero(b) && !a.Equals(b))
			{
				//return Math.Acos(InnerProduct(a,b)/ Norm(a) / Norm(b)); これは理論的にだめ．
				//return Math.Asin(CrossProductScalar(a,b)/Norm(a)/Norm(b)); よさそう？
				//return CrossProductScalar(a,b)/Norm(a)/Norm(b); // 角度微小ならsinに近似可能．
				return Math.Atan2(CrossProductScalar(a,b),InnerProduct(a,b));

			}
			return 0;
		}
		public static double Angle(Point [] ps, Point p)
		{
			double angSum = 0;
			Point p0 = new Point(ps[ps.Length-1].X - p.X, ps[ps.Length-1].Y - p.Y);
			for(int i=0;i<ps.Length;i++)
			{
				Point p1 = new Point(ps[i].X - p.X, ps[i].Y - p.Y);
				double ang = Angle(p0,p1);
				angSum += ang;
				p0=p1;
			}
			return angSum;
		}

		//360度になるかどうかの実験
		/*
			Point [] tps = new Point[8];
			Point tp = new Point(0,0);
			tps[0] = new Point(1,0);
			tps[1] = new Point(1,1);
			tps[2] = new Point(0,1);
			tps[3] = new Point(-1,1);
			tps[4] = new Point(-1,0);
			tps[5] = new Point(-1,-1);
			tps[6] = new Point(0,-1);
			tps[7] = new Point(1,-1);
			bool b = IsEnclosed(tps,tp);
			*/
		//ある点が囲まれているかどうか
		public static bool IsEnclosed(Point [] ps, Point p)
		{
			double angSum = Angle(ps,p);
			angSum = angSum*180/Math.PI;
			if(Math.Abs(angSum) >= 330)
			{
				return true;
			}
			return false;
		}

        public static string InkSerialize(List<Stroke> strokeList, int compressFactor)
        {
            if (compressFactor == 0) compressFactor = 1;
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            bool first2 = true;
            foreach (Stroke s in strokeList)
            {
                if (!first2) sb.Append("*");
                else first2 = false;

                // ink attributes
                sb.Append((s.DrawingAttributes.PenTip == PenTip.Ball) ? "0" : "1");
                sb.Append(":");
                sb.Append(s.DrawingAttributes.Width.ToString());
                sb.Append(":");
                sb.Append(s.DrawingAttributes.Height.ToString());
                sb.Append(":");
                int argb = s.DrawingAttributes.Color.ToArgb();
                sb.Append(argb.ToString());
                sb.Append(":");
                sb.Append(s.DrawingAttributes.Transparency.ToString());
                sb.Append("$");

                // ink points
                bool first = true;
                Point [] ps = s.GetPoints();
                for (int i = 0; i < ps.Length; i = i + compressFactor)
                {
                    if (!first) sb.Append("&");
                    else first = false;
                    sb.Append(ps[i].X.ToString());
                    sb.Append(":");
                    sb.Append(ps[i].Y.ToString());
                }
            }
            return sb.ToString();
        }
        public static List<Stroke> InkDeserialize(Ink ink,string src)
        {
            List<Stroke> outList = new List<Stroke>();
            string[] strokes = src.Split('*');
            foreach (string stroke in strokes)
            {
                string[] dp = stroke.Split('$');
                string[] d = dp[0].Split(':');
                DrawingAttributes da = new DrawingAttributes();
                da.PenTip = (d[0].Equals("0")) ? PenTip.Ball : PenTip.Rectangle;
                da.Width = float.Parse(d[1]);
                da.Height = float.Parse(d[2]);
                da.Color = Color.FromArgb(int.Parse(d[3]));
                da.Transparency = byte.Parse(d[4]);

                //string[] ps = stroke.Split('&');
                string[] ps = dp[1].Split('&');
                Point[] pp = new Point[ps.Length];
                for(int i=0;i<ps.Length;i++){
                    string[] xy = ps[i].Split(':');
                    int x = int.Parse(xy[0]);
                    int y = int.Parse(xy[1]);
                    pp[i] = new Point(x, y);
                }
                Stroke s = ink.CreateStroke(pp);
                s.DrawingAttributes = da;
                outList.Add(s);
            }
            return outList;
        }

	}
}